package com.whu.MenuHub.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.google.gson.Gson;
import com.whu.MenuHub.domain.*;
import com.whu.MenuHub.exception.MenuHubException;
import com.whu.MenuHub.service.*;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

import java.io.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.whu.MenuHub.util.Pair;

@RestController
@CrossOrigin
@RequestMapping("/like")
@Api(tags = "食品推荐信息API")
public class LikePredicController {

    @Autowired
    UserService userService;

    @Autowired
    FoodService foodService;

    @Autowired
    FoodCommentService commentService;
    @Autowired
    UserFlavorService userFlavorService;
    @Autowired
    FoodFlavorService foodFlavorService;

    //获取评分矩阵
    public float[][]getRatingsData() throws MenuHubException{
        //获取全部的用户和食物信息
        List<User> userList=userService.getAllUser();
        List<Food>foodList=foodService.getAllFood();
        //构建一个ratings矩阵
        float [][]ratings=new float[userList.size()][foodList.size()];
        int i=0,j=0;
        for(User user:userList){
            boolean flag=true;
            j=0;
            for(Food food:foodList){
                //根据用户id和食物id获取到全部的评价信息
                List<FoodComment>commentList=commentService.getAllCommentsByUserAndFood(user.getId(), food.getId());
                if(commentList.size()==0){
                    ratings[i][j]=0;
                    j++;
                    continue;
                }
                float ratingsSum=0;
                //循环获取到所有的评分和
                for(FoodComment comment:commentList){
                    ratingsSum+=comment.getFoodscore();
                }
                //将矩阵中的值设置为平均分
                ratings[i][j]=ratingsSum/commentList.size();
                j++;
                //有评价信息才能做协同过滤
                flag=false;
            }
            //如果用户之前不可以根据协同过滤，但现在有评分信息后，可以使用协同过滤
            if(!flag&&!user.isPredictedByFilter()){
                user.setPredictedByFilter(true);
                System.out.println("--------------------------");
                System.out.println("predict-------");
                System.out.println("用户的密码输出："+user.getPassword());
                System.out.println("用户的密码输出："+user.getUsername());
                System.out.println("--------------------------");
                userService.updateUserByID(user,false);
            }
            i++;
        }
        return ratings;
    }

    //每天零点更新协同过滤的矩阵信息
    @Scheduled(cron="0 0 0 * * ?")
    @PostMapping ("")
    public void refreshSimilarity() throws MenuHubException{
        try{
            float[][]ratings=getRatingsData();
            //李妃 python环境
            String anacondaUrl="python";
            //获取python的执行文件地址
            String apiurL=new File(".").getCanonicalPath()+"/python-api/interestCalculate.py";
            String refreshFlag="1";
            //将矩阵转为json格式，以字符串的形式传递给pyhon-api
            Gson gson = new Gson();
            String json = gson.toJson(ratings);
            System.out.println(json);
            //传递的参数
            String[] arg = new String[]{anacondaUrl, apiurL,refreshFlag,json};
            Process proc = Runtime.getRuntime().exec(arg);
            int res = proc.waitFor();
            if (res != 0) {
                //未成功执行 获取错误输出信息
                String line=null;
                BufferedReader inError = new BufferedReader(new InputStreamReader(proc.getErrorStream(), "utf-8"));
                while ((line = inError.readLine()) != null) {
                    System.out.println(line);
                }
                inError.close();
            }
            proc.waitFor();
        }catch (IOException | InterruptedException e){
            throw new MenuHubException(MenuHubException.ErrorType.IO_EXCEPTION,"执行更新相似性矩阵的python接口失败");
        }
    }

    //获得协同过滤对各个食物的推荐分数
    public List<Float> getSimilarityPrediction(Integer userID)throws MenuHubException{
        try{
        //李妃 python环境
        String anacondaUrl="python";
        //获取python的执行文件地址
        String apiurL=new File(".").getCanonicalPath()+"/python-api/interestCalculate.py";
        String refreshFlag="0";
        //传递的参数
        //用户在表中的排序号
        int rankNum=userService.getRankNum(userID);
        String[] arg = new String[]{anacondaUrl, apiurL,refreshFlag,rankNum+""};
        Process proc = Runtime.getRuntime().exec(arg);
        int res = proc.waitFor();
        if (res == 0) {
            //未成功执行 获取错误输出信息
            String line=null;
            List<Float> filterSimList = new ArrayList<>();
            BufferedReader inData = new BufferedReader(new InputStreamReader(proc.getInputStream(), "utf-8"));
            while ((line = inData.readLine()) != null) {
                filterSimList.add(Float.parseFloat(line));
             }
            inData.close();
            return filterSimList;
        }else{
            //未成功执行 获取错误输出信息
            String line=null;
            BufferedReader inError = new BufferedReader(new InputStreamReader(proc.getErrorStream(), "utf-8"));
            while ((line = inError.readLine()) != null) {
                System.out.println(line);
            }
            inError.close();
        }
        proc.waitFor();
        }catch (IOException | InterruptedException e){
            throw new MenuHubException(MenuHubException.ErrorType.IO_EXCEPTION,"获取协同过滤的推荐结果失败");
        }
        return null;
    }

    //根据用户和食品的偏好，用欧式距离求解
    //每个食品一定有口味信息
    public List<Float> getUserAndFoodSim(UserFlavor userFlavor){
        List<Float>userAndFoodSim=new ArrayList<Float>();
        //获取所有的食物口味信息
        List<FoodFlavor>foodFlavorList=foodFlavorService.getAllFoodFlavor();
        int foodNum=foodFlavorList.size();
        float maxdiff=0;
        //最大的差距不会超过100
        float mindiff=100;
        for(int i=0;i<foodNum;i++){
            FoodFlavor foodFlavor=foodFlavorList.get(i);
            //用欧式距离求用户偏好和口味之间的差距
            float diffenence=Math.abs(foodFlavor.getAcid()-userFlavor.getAcid())+Math.abs(foodFlavor.getSpicy()-userFlavor.getSpicy())+
                    Math.abs(foodFlavor.getSweet()-userFlavor.getSweet())+Math.abs(foodFlavor.getSalted()-userFlavor.getSalted())+
                    Math.abs(foodFlavor.getHeavyTaste()-userFlavor.getHeavyTaste())+Math.abs(foodFlavor.getHotLevel()-userFlavor.getHotLevel());
            userAndFoodSim.add(diffenence);
            //更新最大最小值
            if(diffenence>maxdiff)maxdiff=diffenence;
            if(diffenence<mindiff)mindiff=diffenence;
        }
        //获取到映射区间
        float size=maxdiff-mindiff;
        //将值映射到【0，1】区间，且因为求的是相似矩阵，所以要1-
        for(int i=0;i<foodNum;i++){
            float different=userAndFoodSim.get(i);
            userAndFoodSim.set(i, 1-(different-mindiff)/size);
        }
        return userAndFoodSim;
    }


    //根据用户id获取推荐的食品，直接返回list
    @GetMapping("/{id}")
    @ApiOperation("根据用户id获取推荐食品")
    @PreAuthorize("hasAnyAuthority('all',#id.toString())")
    public List<Food> getOrderByUserID(@ApiParam("用户ID")@PathVariable Integer id)throws MenuHubException{

        List<Float>commentScore=new ArrayList<>();
        List<Food>foodList=foodService.getAllFood();
        int foodNum=foodList.size();
        //分别是协同过滤推荐的权重，用户食物相似度推荐权重和点赞量推荐权重
        //double[]weight=new double[]{0.5,0.3,0.2};

        //获取到所有食物的点赞量，并且将点赞量映射到0，1区间内,点赞量作为推荐
//        List<Float>foodStar=new ArrayList<>();
//        int maxStar=0;
//        int minStar=0x7fff;
//        for(int i=0;i<foodNum;i++){
//            int star =foodList.get(i).getFoodStar();
//            foodStar.add((float) star);
//            if(star>maxStar)maxStar=star;
//            if(star<minStar)minStar=star;
//        }
//        int size=maxStar-minStar;
//        for(int i=0;i<foodNum;i++){
//            foodStar.set(i,(foodStar.get(i)-minStar)/size);
//            commentScore.add((float) (0.2*foodStar.get(i)));
//        }

        //获取到所有食物的评分，并且将评分映射到0，1区间内,评分作为推荐
        List<Float>foodScore=new ArrayList<>();
        float maxScore=0;
        float minScore=0x7fff;
        for(int i=0;i<foodNum;i++){
            float score =foodList.get(i).getFoodScore();
            foodScore.add(score);
            if(score>maxScore)maxScore=score;
            if(score<minScore)minScore=score;
        }
        float size=maxScore-minScore;
        for(int i=0;i<foodNum;i++){
            foodScore.set(i,(foodScore.get(i)-minScore)/size);
            //将评分根据权值放到推荐的list中
            commentScore.add((float) (0.2*foodScore.get(i)));
        }
        //获得协同过滤对各个食物的推荐分数
        User user=userService.getUserByUserID(id);
        if(user!=null&&user.isPredictedByFilter()){
            List<Float> filterSimList=getSimilarityPrediction(id);
            for(int i=0;i<foodNum;i++){
                commentScore.set(i,commentScore.get(i)+(float) (0.5*filterSimList.get(i)));
            }
        }
        //获取欧式距离计算用户偏好和食品口味的相似度
        UserFlavor userFlavor=userFlavorService.getfalvorByUserID(id);
        if(userFlavor!=null){
            List<Float>userAndFoodSim=getUserAndFoodSim(userFlavor);
            for(int i=0;i<foodNum;i++){
                commentScore.set(i, commentScore.get(i)+(float) (0.3*userAndFoodSim.get(i)));
            }
        }else{
            //获取和用户性别相同的用户对商品的平均评分，返回的平均评分按照foodID排序
            List<Float>userWithGender=commentService.getFoodAveScoreByGender(user.getGender());
            //获取和用户籍贯相同的用户对商品的平均评分，返回的平均评分按照foodID排序
            List<Float>userWithAddress=commentService.getFoodAveScoreByAddress(user.getNativeplace());
            for(int i=0;i<foodNum;i++){
                float genderSorce=0;
                float addressScore=0;
                if(userWithGender.get(i)!=null){
                    genderSorce=(float)userWithGender.get(i);
                }
                if(userWithAddress.get(i)!=null){
                    addressScore=(float)userWithAddress.get(i);
                }
                commentScore.set(i, commentScore.get(i)+(float) (0.15*genderSorce+0.15*addressScore));
            }
        }
        // 排序并获取原来序号信息,从大到小排序
        List<Pair<Float, Food>> sortedList = new ArrayList<>();
        for (int i = 0; i < commentScore.size(); i++) {
            sortedList.add(new Pair<>(commentScore.get(i), foodList.get(i)));
        }
        Collections.sort(sortedList, (o1, o2) -> o2.getKey().compareTo(o1.getKey()));
        //排好序后，按照递减顺序获取到推荐的食物
        List<Food> returnList=new ArrayList<>();
        for (int i=0;i<sortedList.size();i++){
            returnList.add(sortedList.get(i).getValue());
        }
        return returnList;
    }
}

//用于排序的类
